﻿using Micro.Extensions.Utilities;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Threading;

namespace Micro.Extensions.Log
{
    /// <summary>
    /// 实现ILogger
    /// </summary>
    public class MicroLogger : ILogger
    {

        public string Name { get; internal set; }
        public string ApplicationId { get; internal set; }
        public Func<string, LogLevel, bool> Filter { get; internal set; }
        public bool IsConsole { get; internal set; }

        private readonly MicroLoggerProcessor _queueProcessor;
        private static readonly string HostIpAddress;
        internal IExternalScopeProvider ScopeProvider { get; set; }
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly IFileDiskWriter _fileWriter;


        static MicroLogger()
        {
            HostIpAddress = NetWorkUtil.GetIpAddress();
        }

        public MicroLogger(string name,
            string applicationId,
            bool console,
            Func<string, LogLevel, bool> filter,
            IExternalScopeProvider scopeProvider,
            MicroLoggerProcessor queueProcessor,
            IHttpContextAccessor httpContextAccessor,
            IFileDiskWriter fileWriter)
        {

            Name = name;
            ApplicationId = applicationId;
            IsConsole = console;
            Filter = filter ?? ((category, logLevel) => true);
            _queueProcessor = queueProcessor;
            ScopeProvider = scopeProvider;
            _httpContextAccessor = httpContextAccessor;
            _fileWriter = fileWriter;
        }

        public IDisposable BeginScope<TState>(TState state) => ScopeProvider?.Push(state);

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            if (!IsEnabled(logLevel)) return;
            WriteMessage(logLevel, eventId, state, exception);
        }

        private void WriteMessage<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception)
        {
            try
            {
                BaseMessage message;
                if (state is BaseMessage baseMessage)
                    message = baseMessage;
                else
                {
                    message = new BaseMessage
                    {
                        Message = state.ToString()
                    };
                }

                BuildMessage(logLevel, eventId, exception, message);
                _queueProcessor.EnqueueMessage(message);

                if (IsConsole)
                    ConsolePrint(logLevel, message);
            }
            catch (Exception e)
            {
                var errMsg = new BaseMessage
                {
                    LogLevel = "fail",
                    LogName = nameof(MicroLoggerProcessor),
                    HostIPAddress = HostIpAddress,
                    LogTime = DateTime.Now,
                    EventId = eventId.Id,
                    ApplicationId = ApplicationId,
                    Exception = e.ToString(),
                    LogType = eventId.ToString(),
                    ThreadId = Thread.CurrentThread.ManagedThreadId,
                    ThreadName = Thread.CurrentThread.Name,
                    ChainId = _httpContextAccessor.TryGetHeaderValue(LogConst.ChainId),
                    TrackId = _httpContextAccessor.TryGetHeaderValue(LogConst.TrackId),
                    ParentTrackId = _httpContextAccessor.TryGetHeaderValue(LogConst.ParentTrackId),
                    Message = $"写入日志错误，原始日志信息：{state}",
                };

                _fileWriter?.Write(new HardDiskInfo { FileName = LogConst.ErrorLog, Message = errMsg });
            }
        }

        private void BuildMessage(LogLevel logLevel, EventId eventId, Exception exception, BaseMessage message)
        {
            message.LogName = Name;
            message.LogLevel = GetLogLevelString(logLevel);
            message.HostIPAddress = HostIpAddress;
            message.ApplicationId = ApplicationId;
            message.EventId = eventId.Id;
            message.LogType = eventId.ToString();
            message.Exception = exception?.ToString();
            message.LogTime = DateTime.Now;
            message.ThreadId = Thread.CurrentThread.ManagedThreadId;
            message.ThreadName = Thread.CurrentThread.Name;
            message.ChainId = _httpContextAccessor.TryGetHeaderValue(LogConst.ChainId);
            message.TrackId = _httpContextAccessor.TryGetHeaderValue(LogConst.TrackId);
            message.ParentTrackId = _httpContextAccessor.TryGetHeaderValue(LogConst.ParentTrackId);
        }

        private void ConsolePrint(LogLevel logLevel, BaseMessage message)
        {

            var color = ConsoleColor.DarkGreen;
            switch (logLevel)
            {
                case LogLevel.Trace:
                    break;
                case LogLevel.Debug:
                    break;
                case LogLevel.Information:
                    break;
                case LogLevel.Warning:
                    color = ConsoleColor.Yellow;
                    break;
                case LogLevel.Error:
                    color = ConsoleColor.Red;
                    break;
                case LogLevel.Critical:
                    color = ConsoleColor.Red;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
            Console.ForegroundColor = color;
            Console.WriteLine("");
            Console.WriteLine("===================================================================================");
            //var jsonFormatted = JToken.Parse(message.ToJson()).ToString(Formatting.Indented);
            Console.WriteLine(message.ToIndentedJson());
            Console.Write("");
        }

        public bool IsEnabled(LogLevel logLevel)
        {
            if (logLevel == LogLevel.None) return false;
            return Filter(Name, logLevel);
        }

        private static string GetLogLevelString(LogLevel logLevel)
        {
            switch (logLevel)
            {
                case LogLevel.Trace:
                    return "trce";
                case LogLevel.Debug:
                    return "dbug";
                case LogLevel.Information:
                    return "info";
                case LogLevel.Warning:
                    return "warn";
                case LogLevel.Error:
                    return "fail";
                case LogLevel.Critical:
                    return "crit";
                default:
                    throw new ArgumentOutOfRangeException(nameof(logLevel));
            }
        }
    }
}
